#include "render.hpp"
#include "unit_utils.hpp"
#include "utils.hpp"
#include "offsets.hpp"
#include "main.hpp"
#include "logging.hpp"
#include "player_cache.hpp"
#include <algorithm>
#include <cstring>

namespace perf_boost {

// External references to variables from main.cpp
    extern std::vector<PlayerData> resolvedPlayers;
    extern std::vector<PlayerData> alwaysRenderPlayersToCheck;
    extern std::vector<PlayerData> unresolvedPlayers;
    extern std::vector<PlayerData> neverRenderResolvedPlayers;
    extern std::vector<PlayerData> neverRenderPlayersToCheck;
    extern std::vector<PlayerData> neverRenderUnresolvedPlayers;
    extern PlayerCache gPlayerCache;
    extern std::map<uint64_t, bool> playersWithAggro;
    extern std::set<uint64_t> playersWithAggroThisFrame;

    extern bool hideAllPlayers;
    extern bool showAllPlayers;
    extern bool alwaysRenderRaidMarks;
    extern bool alwaysRenderPVP;
    extern bool alwaysRenderPlayersWithAggro;
    extern int playerRenderDist;
    extern int playerRenderDistInCombat;
    extern int playerRenderDistInCities;
    extern int trashUnitRenderDist;
    extern int trashUnitRenderDistInCombat;
    extern int petRenderDist;
    extern int petRenderDistInCombat;
    extern int summonRenderDist;
    extern int summonRenderDistInCombat;
    extern int corpseRenderDist;

    extern bool gPlayerInCombat;
    extern bool gPlayerInCity;
    extern C3Vector gPlayerPosition;

    bool shouldAlwaysRenderPlayer(uintptr_t *unitPtr, uint64_t unitGuid) {
        // Check resolved players first (fastest lookup)
        for (const auto &player: resolvedPlayers) {
            if (player.guid == unitGuid) {
                return true;
            }
        }

        // Check alwaysRenderPlayersToCheck and try to resolve them
        if (!alwaysRenderPlayersToCheck.empty()) {
            char *unitName = UnitGetName(unitPtr);
            if (unitName) {
                auto it = alwaysRenderPlayersToCheck.begin();
                while (it != alwaysRenderPlayersToCheck.end()) {
                    if (strcmp(it->name, unitName) == 0) {
                        // Found match, move to resolved players
                        PlayerData resolvedPlayer = *it;
                        resolvedPlayer.guid = unitGuid;
                        resolvedPlayer.resolved = true;
                        resolvedPlayers.push_back(resolvedPlayer);

                        // Remove from alwaysRenderPlayersToCheck and unresolvedPlayers
                        alwaysRenderPlayersToCheck.erase(it);
                        auto unresolvedIt = std::find_if(unresolvedPlayers.begin(), unresolvedPlayers.end(),
                                                         [&](const PlayerData &p) {
                                                             return strcmp(p.name, unitName) == 0;
                                                         });
                        if (unresolvedIt != unresolvedPlayers.end()) {
                            unresolvedPlayers.erase(unresolvedIt);
                        }

                        DEBUG_LOG("Resolved player " << unitName << " with GUID: " << std::hex << unitGuid << std::dec);
                        gPlayerCache.AddPlayerMapping(unitName, unitGuid);
                        return true;
                    } else {
                        ++it;
                    }
                }
            }
        }

        return false;
    }

    bool shouldNeverRenderPlayer(uintptr_t *unitPtr, uint64_t unitGuid) {
        // Check resolved players first (fastest lookup)
        for (const auto &player: neverRenderResolvedPlayers) {
            if (player.guid == unitGuid) {
                return true; // Found in blacklist, never render
            }
        }

        // Check neverRenderPlayersToCheck and try to resolve them
        if (!neverRenderPlayersToCheck.empty()) {
            char *unitName = UnitGetName(unitPtr);
            if (unitName) {
                auto it = neverRenderPlayersToCheck.begin();
                while (it != neverRenderPlayersToCheck.end()) {
                    if (strcmp(it->name, unitName) == 0) {
                        // Found match, move to resolved players
                        PlayerData resolvedPlayer = *it;
                        resolvedPlayer.guid = unitGuid;
                        resolvedPlayer.resolved = true;
                        neverRenderResolvedPlayers.push_back(resolvedPlayer);

                        // Remove from neverRenderPlayersToCheck and neverRenderUnresolvedPlayers
                        neverRenderPlayersToCheck.erase(it);
                        auto unresolvedIt = std::find_if(neverRenderUnresolvedPlayers.begin(),
                                                         neverRenderUnresolvedPlayers.end(),
                                                         [&](const PlayerData &p) {
                                                             return strcmp(p.name, unitName) == 0;
                                                         });
                        if (unresolvedIt != neverRenderUnresolvedPlayers.end()) {
                            neverRenderUnresolvedPlayers.erase(unresolvedIt);
                        }

                        DEBUG_LOG(
                                "Resolved never-render player " << unitName << " with GUID: " << std::hex << unitGuid << std::dec);
                        gPlayerCache.AddPlayerMapping(unitName, unitGuid);
                        return true; // Found in blacklist, never render
                    } else {
                        ++it;
                    }
                }
            }
        }

        return false; // Not in blacklist, allow rendering
    }

    bool ShouldRenderBasedOnDistance(uintptr_t *this_ptr, int renderDist) {
        if (renderDist == 0) {
            return false; // if dist 0 immediately return false
        } else if (renderDist > 0) {
            auto distance = ApproximateDistanceBetween(UnitGetPosition(this_ptr), gPlayerPosition);
            return distance < renderDist;
        }
        return true; // default to true if renderDist is negative
    }

    uint32_t shouldRenderPlayer(uintptr_t *unitPtr) {
        if (showAllPlayers) {
            return 1; // Show all players (takes priority over hide all players)
        }
        if (hideAllPlayers) {
            return 0; // Hide all players
        }
        auto unitGuid = UnitGetGuid(unitPtr);
        if (alwaysRenderRaidMarks) {
            auto raidMark = GetRaidMarkForGuid(unitGuid);

            if (raidMark > 0) {
                // always render players with raid marks
                return 1;
            }
        }

        // check if this player is in NeverRenderPlayers blacklist
        if (shouldNeverRenderPlayer(unitPtr, unitGuid)) {
            return 0; // Force hide this player
        }

        // always show MC players
        if (UnitIsCharmed(unitPtr)) {
            return 1;
        }

        // always show PvP-flagged players if cvar on
        if (alwaysRenderPVP && UnitIsPvpFlagged(unitPtr)) {
            // check if attackable
            // get fresh unit ptr to avoid issues on loading screens
            auto playerPtr = GetObjectPtr(ClntObjMgrGetActivePlayerGuid());
            if (playerPtr) {
                // only show if attackable so we don't show pvp players in group/raid
                if (UnitCanAttackUnit(playerPtr, unitPtr)) {
                    return 1;
                }
            }
        }

        // check if this player is in AlwaysRenderPlayers list
        if (shouldAlwaysRenderPlayer(unitPtr, unitGuid)) {
            return 1;
        }

        // check if we should always render players with aggro
        if (alwaysRenderPlayersWithAggro) {
            auto aggroIt = playersWithAggro.find(unitGuid);
            if (aggroIt != playersWithAggro.end() && aggroIt->second) {
                return 1;
            } else {
                // Initialize player in aggro map if not already there
                if (aggroIt == playersWithAggro.end()) {
                    playersWithAggro[unitGuid] = false;
                }
            }
        }

        int renderDist;
        if (gPlayerInCombat && playerRenderDistInCombat != -1) {
            renderDist = playerRenderDistInCombat;
        } else if (gPlayerInCity && playerRenderDistInCities != -1) {
            renderDist = playerRenderDistInCities;
        } else {
            renderDist = playerRenderDist;
        }
        return ShouldRenderBasedOnDistance(unitPtr, renderDist);
    }

    uint32_t shouldRenderCorpse(uintptr_t *unitPtr) {
        auto lootMethod = *reinterpret_cast<uint32_t *>(Offsets::LootMethod);
        if (lootMethod >= 3 || lootMethod == 1) {
            return 1; // always render corpses if loot method is group loot, need before greed, or round robin
        }

        auto unitLevel = UnitGetLevel(unitPtr);
        if (unitLevel >= 63) {
            // don't hide boss corpses
            return 1;
        }

        int renderDist = corpseRenderDist;
        return ShouldRenderBasedOnDistance(unitPtr, renderDist);
    }

    uint32_t shouldRenderUnit(uintptr_t *unitPtr) {
        if (alwaysRenderRaidMarks) {
            auto raidMark = GetRaidMarkForGuid(UnitGetGuid(unitPtr));

            if (raidMark > 0) {
                // always render raid marks
                return 1;
            }
        }

        auto isDead = UnitIsDead(unitPtr);
        if (isDead && corpseRenderDist != -1) {
        // some corpses (lootable ones?) are dead units
            return shouldRenderCorpse(unitPtr);
        } else if(!isDead) {
            // Check if it's a pet (controlled by player)
            if (UnitIsControlledByPlayer(unitPtr)) {
                auto *unitFields = *reinterpret_cast<UnitFields **>(unitPtr + 68);

                // always show your own summons
                if (unitFields->summonedBy != ClntObjMgrGetActivePlayerGuid()) {
                    if (unitFields->petNameTimestamp > 0) {
                        // This is a pet with a name
                        int renderDist = (gPlayerInCombat && petRenderDistInCombat != -1)
                                         ? petRenderDistInCombat
                                         : petRenderDist;
                        return ShouldRenderBasedOnDistance(unitPtr, renderDist);
                    } else {
                        // This is a summon (player-controlled unit without name)
                        if (summonRenderDist != -1) {
                            int renderDist = (gPlayerInCombat && summonRenderDistInCombat != -1)
                                             ? summonRenderDistInCombat
                                             : summonRenderDist;
                            return ShouldRenderBasedOnDistance(unitPtr, renderDist);
                        }
                    }
                }
            }

            auto unitLevel = UnitGetLevel(unitPtr);
            if (unitLevel < 63) {
                int renderDist = (gPlayerInCombat && trashUnitRenderDistInCombat != -1)
                                 ? trashUnitRenderDistInCombat : trashUnitRenderDist;
                return ShouldRenderBasedOnDistance(unitPtr, renderDist);
            }
        }

        return 1; // Default to rendering the unit
    }
}